#include "audiooutput.h"

namespace sct
{
CAudioOutput::CAudioOutput(const int sample_rate):
    mMutex(NULL),
    mSize(0),
    mThd(NULL),
    mState(Initing),
    mPlaybackHandle(NULL),
    mDelay2RenderArg(0),
    mSampleRate(sample_rate)
{
    mMutex = new CMutex;
    mSilentTime = currentTime() + 2;
    mLastTimePutIn = currentTime();

    mThd = new CThread("audio_play_back");
    mThd->addCallback(this);

    mState = Playing;
    mThd->start();
}

int CAudioOutput::main(CThread * caller)
{
    if(caller == mThd)
    {
        snd_pcm_sframes_t frames_to_deliver;

        ///open device
        snd_pcm_hw_params_t *hw_params;
        snd_pcm_sw_params_t *sw_params;
        int err;
        if ((err = snd_pcm_open (&mPlaybackHandle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
            SCT_LOGD( "cannot open audio device %s (%s)\n",
                    "default",
                    snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
           SCT_LOGD(  "cannot allocate hardware parameter structure (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_any (mPlaybackHandle, hw_params)) < 0) {
            SCT_LOGD( "cannot initialize hardware parameter structure (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_set_access (mPlaybackHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
            SCT_LOGD( "cannot set access type (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params_set_format (mPlaybackHandle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
            SCT_LOGD( "cannot set sample format (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        unsigned rate = mSampleRate;
        int dir = 0;
        if ((err = snd_pcm_hw_params_set_rate_near (mPlaybackHandle, hw_params, &rate, &dir)) < 0) {
            SCT_LOGD( "cannot set sample rate (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        SCT_LOGD("audio sample_rate:%d", rate);

        if ((err = snd_pcm_hw_params_set_channels (mPlaybackHandle, hw_params, 2)) < 0) {
            SCT_LOGD( "cannot set channel count (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        if ((err = snd_pcm_hw_params (mPlaybackHandle, hw_params)) < 0) {
            SCT_LOGD( "cannot set parameters (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        snd_pcm_hw_params_free (hw_params);

        /* tell ALSA to wake us up whenever 4096 or more frames
               of playback data can be delivered. Also, tell
               ALSA that we'll start the device ourselves.
         */

        if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
            SCT_LOGD( "cannot allocate software parameters structure (%s)\n",
                    snd_strerror (err));
            exit (1);
        }
        if ((err = snd_pcm_sw_params_current (mPlaybackHandle, sw_params)) < 0) {
            SCT_LOGD( "cannot initialize software parameters structure (%s)\n",
                    snd_strerror (err));
            exit (1);
        }
        if ((err = snd_pcm_sw_params_set_avail_min (mPlaybackHandle, sw_params, 12288)) < 0) {
            SCT_LOGD( "cannot set minimum available count (%s)\n",
                    snd_strerror (err));
            exit (1);
        }
        if ((err = snd_pcm_sw_params_set_start_threshold (mPlaybackHandle, sw_params, 0U)) < 0) {
            SCT_LOGD( "cannot set start mode (%s)\n",
                    snd_strerror (err));
            exit (1);
        }
        if ((err = snd_pcm_sw_params (mPlaybackHandle, sw_params)) < 0) {
            SCT_LOGD( "cannot set software parameters (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        /* the interface will interrupt the kernel every 4096 frames, and ALSA
               will wake up this program very soon after that.
         */

        if ((err = snd_pcm_prepare (mPlaybackHandle)) < 0) {
            SCT_LOGD( "cannot prepare audio interface for use (%s)\n",
                    snd_strerror (err));
            exit (1);
        }

        /* wait till the interface is ready for data, or 1 second
               has elapsed.
         */
        if ((err = snd_pcm_wait (mPlaybackHandle, 1000)) < 0) {
            SCT_LOGD( "poll failed (%s)\n", strerror (errno));
            exit (1);
        }

        /* find out how much space is available for playback data */

        if ((frames_to_deliver = snd_pcm_avail_update (mPlaybackHandle)) < 0) {
            if (frames_to_deliver == -EPIPE) {
                SCT_LOGD( "an xrun occured\n");
            } else {
                SCT_LOGD( "unknown ALSA avail update return value (%d)\n",
                        frames_to_deliver);
            }
            exit(1);
        }

        const int maxFreeFrames = frames_to_deliver;


        while(Playing == mState)
        {
            /* wait till the interface is ready for data, or 1 second
                   has elapsed.
             */
            if ((err = snd_pcm_wait (mPlaybackHandle, 1000)) < 0) {
                SCT_LOGD( "poll failed (%s)\n", strerror (errno));

                mSampleRate = mSampleRate + 1;
                return 1;
            }

            /* find out how much space is available for playback data */

            if ((frames_to_deliver = snd_pcm_avail_update (mPlaybackHandle)) < 0) {
                if (frames_to_deliver == -EPIPE) {
                    SCT_LOGD( "an xrun occured\n");
                } else {
                    SCT_LOGD( "unknown ALSA avail update return value (%d)\n",
                            frames_to_deliver);
                }
                
                usleep(100000);
                frames_to_deliver = snd_pcm_recover(mPlaybackHandle, frames_to_deliver, 0);
                SCT_LOGD("Try to Recover ret:%d", frames_to_deliver);
                continue;
            }

            bool bNeedNullPkt = true;
            mMutex->lock();
            if(mSize>0)
            {
                mLastTimePutIn = currentTime();

                int ret = 0;
                snd_pcm_sframes_t nDelayFrames = 0;
                ret = snd_pcm_delay(mPlaybackHandle, &nDelayFrames);
                if(0 == ret)
                {
                    double d = 1.0*size2frameNum(mSize)/mSampleRate + 1.0 * nDelayFrames/mSampleRate;

//                    SCT_LOGD("==============my:%10d, %.3f           hw:%10d, %.3f            total:%.3f",
//                             mSize,
//                             1.0*size2frameNum(mSize)/mSampleRate,
//                             nDelayFrames,
//                             1.0 * nDelayFrames/mSampleRate,
//                             d);

                    if(d > 0.4)
                    {
                        snd_pcm_state_t state = snd_pcm_state(mPlaybackHandle);

                        SCT_LOGD("state:%d => drop pcm frame:%.3f", d, state);
                        mSilentTime = currentTime() + 2;

                        if(d > 0.7)
                        {
                            ///try to restart audiooutput
                            SCT_LOGD("play pcm error, restart");
                            mSampleRate = mSampleRate + 1;

                            ///release the locker
                            mMutex->unlock();
                            return 2;
                        }
                    }
                }

                bNeedNullPkt = false;

                CItem * item = mDatas.front();

                int nframes = size2frameNum(item->size - item->pos);
                frames_to_deliver = frames_to_deliver<nframes?frames_to_deliver:nframes;

                if ((err = snd_pcm_writei (mPlaybackHandle, item->data + item->pos, frames_to_deliver)) < 0) {
                    SCT_LOGD( "write failed (%s)\n", snd_strerror (err));
                }

                int sz = frameNum2size(frames_to_deliver);
                item->pos += sz;

                if(item->pos == item->size)
                {
                    CItem::free(item);
                    mDatas.pop_front();
                }

                mSize -= sz;
            }
            mMutex->unlock();

            if(currentTime() - mLastTimePutIn < 0.1)
            {
                ///wait for incoming pcm
                bNeedNullPkt = false;
            }

            int needToFeed = 0;

            if(bNeedNullPkt)
            {
                const int base = 2048*3;

                char buf[base*4];

                needToFeed = base - (maxFreeFrames - frames_to_deliver);
                if(needToFeed > 0)
                {
                    needToFeed = needToFeed<base?needToFeed:base;
                }

                if(needToFeed > 0)
                {
//                    memset(buf, 100, sizeof(buf));///make some noise
                    memset(buf, 0, sizeof(buf));///keep silent
                    if ((err = snd_pcm_writei (mPlaybackHandle, buf, needToFeed)) < 0) {
                        SCT_LOGD("write failed (%s)\n", snd_strerror (err));
                    }

                }
            }

            if(0 == needToFeed)
            {
                usleep(10000);
            }
        }
    }
    return 0;
}

int CAudioOutput::size2frameNum(const int sz)
{
    return sz/(2*2);
}

int CAudioOutput::frameNum2size(const int num)
{
    return num*2*2;
}

CAudioOutput::~CAudioOutput()
{
    mState = Terminated;
    SCT_DELETE(mThd);

    while(!mDatas.empty())
    {
        CItem * item = mDatas.front();
        mDatas.pop_front();

        CItem::free(item);
    }

    SCT_DELETE(mMutex);

    if(mPlaybackHandle)
    {
        snd_pcm_close(mPlaybackHandle);
    }
}

void CAudioOutput::pushData(const void * d, const int len, const CDataInfo & info)
{
    CAutoLock locker(mMutex);

    if(info.sample_rate != mSampleRate)
    {
        ///reset audio player
        SCT_LOGD("reset audio player");

        locker.unlock();

        ///stop it
        mState = Terminated;
        SCT_DELETE(mThd);

        locker.relock();

        while(!mDatas.empty())
        {
            CItem * item = mDatas.front();
            mDatas.pop_front();

            CItem::free(item);
        }

        if(mPlaybackHandle)
        {
            snd_pcm_drain(mPlaybackHandle);
            snd_pcm_drop(mPlaybackHandle);
            snd_pcm_hw_free(mPlaybackHandle);
            snd_pcm_close(mPlaybackHandle);

            usleep(500000);
        }

        ///start it
        mSize = 0;
        mThd = NULL;
        mState = Initing;
        mPlaybackHandle = NULL;
        mSampleRate = info.sample_rate;

        mThd = new CThread("audio_play_back");
        mThd->addCallback(this);

        mState = Playing;
        mThd->start();

        mSilentTime = currentTime()+1;
        mLastTimePutIn = currentTime();

        return;
    }

    if(currentTime() < mSilentTime)
    {
        ///keep silent
        return;
    }

    mSize += len;

    CItem * item = new CItem;

    item->size = len;

    item->data = new uint8_t[item->size];
    memcpy(item->data, d, item->size);

    item->pos = 0;

    mDatas.push_back(item);
}

int64_t CAudioOutput::delay2Render()
{
    return 170000;
}

}
